﻿using ArtMath.Attribute;
using ArtMath.Component.CoordinateSystem;
using ArtMath.Component.Manager;
using ArtMath.Component.Point;
using ArtMath.Converter;
using ArtMath.Core.Data;
using ArtMath.Core.Helper;
using ArtMath.PointCollection;
using ArtMath.PointCollection.Helper;
using ArtMath.Resource;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using ArtMath.CommonType;

namespace ArtMath.Component.Line
{
    /// <summary>
    /// 直线基类
    /// </summary>
    [Serializable]
    public abstract class StraightLineBase : LineBase
    {
        /// <summary>
        /// 获取或设置在创建过程中是否允许吸附到15°的倍数角上
        /// </summary>
        public bool AllowFix = true;
        PropertyDisplayer kDisplayer, kAngleDisplayer;
        protected StraightLine ske = new StraightLine(), pske = new StraightLine();
        public StraightLineBase(SketchPad pad, string name, ToolTypeName toolType, bool addToList = true) : base(pad, name, toolType, addToList)
        {
            SlopeDisplayer = new PropertyDisplayer(this, "Slope");
            SlopeAngleDisplayer = new PropertyDisplayer(this, "SlopeAngle");
        }
        public override PointCollectionsManager Skeleton
        {
            get
            {
                return new PointCollectionsManager(ske);
            }
        }
        public override PointCollectionsManager PSkeleton
        {
            get
            {
                return new PointCollectionsManager(pske);
            }
        }
        public override PointCollectionsManager NewSkeleton
        {
            get
            {
                return new PointCollectionsManager(new StraightLine(NewLocation, NewSlope));
            }
        }
        public override PointCollectionsManager NewPSkeleton
        {
            get
            {
                return new PointCollectionsManager(new StraightLine(NewPLocation, NewPSlope));
            }
        }
        protected override bool DoAngleFix
        {
            get { return AllowFix; }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("SlopeAngle"), TypeConverter(typeof(IntelliAngleConverter))]
        public double SlopeAngle { get; private set; }
        [Browsable(false)]
        public PointD Location
        {
            get { return ske.Location; }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("Slope"), ReadOnly(true)]
        public virtual double Slope
        {
            get { return ske.Slope; }
            set
            {
                if (double.IsNegativeInfinity(value)) value = double.PositiveInfinity;
                ske.Slope = value;
                SlopeAngle = Utility.GetSlopeAngle(ske.Slope);
            }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "Slope", "Displayer")]
        public PropertyDisplayer SlopeDisplayer
        {
            get { return kDisplayer; }
            set { kDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "SlopeAngle", "Displayer")]
        public PropertyDisplayer SlopeAngleDisplayer
        {
            get { return kAngleDisplayer; }
            set { kAngleDisplayer = value; }
        }
        [Browsable(false)]
        public virtual double ArrivalAngle
        {
            get
            {
                if (InPoints?.Length == 2)
                {
                    return InPoints[0].NewXY.GetAngle(InPoints[1].NewXY);
                }
                double a90 = Math.Abs(Math.Atan(NewSlope));
                return Slope >= 0 ? a90 : Math.PI - a90;
            }
        }
        [Browsable(false)]
        public PointD PLocation
        {
            get { return pske.Location; }
        }
        [Browsable(false)]
        public virtual double PSlope
        {
            get { return pske.Slope; }
            set { pske.Slope = value; }
        }
        [Browsable(false)]
        public virtual double NewSlope
        {
            get { return Slope; }
        }

        [Browsable(false)]
        public double A
        {
            get { return double.IsInfinity(Slope) ? 1 : Slope; }
        }
        [Browsable(false)]
        public double NewA
        {
            get { return double.IsInfinity(NewSlope) ? 1 : NewSlope; }
        }
        [Browsable(false)]
        public double B
        {
            get { return double.IsInfinity(Slope) ? 0 : -1; }
        }
        [Browsable(false)]
        public double NewB
        {
            get { return double.IsInfinity(NewSlope) ? 0 : -1; }
        }
        [Browsable(false)]
        public double C
        {
            get { return double.IsInfinity(Slope) ? -ske.Location.X : ske.Location.Y - Slope * ske.Location.X; }
        }
        [Browsable(false)]
        public double NewC
        {
            get { return double.IsInfinity(NewSlope) ? -NewLocation.X : NewLocation.Y - NewSlope * NewLocation.X; }
        }
        [Browsable(false)]
        public double Pa
        {
            get { return double.IsInfinity(PSlope) ? 1 : PSlope; }
        }
        [Browsable(false)]
        public double NewPa
        {
            get { return double.IsInfinity(NewPSlope) ? 1 : NewPSlope; }
        }
        [Browsable(false)]
        public double Pb
        {
            get { return double.IsInfinity(PSlope) ? 0 : -1; }
        }
        [Browsable(false)]
        public double NewPb
        {
            get { return double.IsInfinity(NewPSlope) ? 0 : -1; }
        }
        [Browsable(false)]
        public double Pc
        {
            get { return double.IsInfinity(PSlope) ? -pske.Location.X : pske.Location.Y - PSlope * pske.Location.X; }
        }
        [Browsable(false)]
        public double NewPc
        {
            get { return double.IsInfinity(NewPSlope) ? -NewPLocation.X : NewPLocation.Y - NewPSlope * NewPLocation.X; }
        }
        [Browsable(false)]
        public virtual double NewPSlope
        {
            get { return PSlope; }
        }
        [Browsable(false)]
        public virtual PointD NewPLocation
        {
            get { return InPoints?.Length > 0 ? InPoints[0].NewPxy : pske.Location; }
        }
        [Browsable(false)]
        public virtual PointD NewLocation
        {
            get { return InPoints?.Length > 0 ? InPoints[0].NewXY : ske.Location; }
        }
        [Browsable(false)]
        public PointD NormalVector
        {
            get { return new PointD(A, B); }
        }
        public double GetArrivalAngle(StraightLineBase line)
        {
            return Utility.GetArrivalAngle(Slope, line.Slope);
        }
        public override void PaintProperties(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible)
            {
                SlopeDisplayer.Paint(gra);
                SlopeAngleDisplayer.Paint(gra);
            }
            base.PaintProperties(gra);
        }
        public override void RefreshPropertyDisplayersLocation()
        {
            SlopeDisplayer.RefreshLocation();
            SlopeAngleDisplayer.RefreshLocation();
            base.RefreshPropertyDisplayersLocation();
        }
        public override void RefreshPropertyDisplayersText()
        {
            SlopeDisplayer.RefreshText();
            SlopeAngleDisplayer.RefreshText();
            base.RefreshPropertyDisplayersText();
        }
        public virtual PointBase GetCommonPoint(StraightLineBase line)
        {
            List<PointBase> pts1 = new List<PointBase>(), pts2 = new List<PointBase>();
            if (InPoints != null)
            {
                pts1.AddRange(InPoints);
            }
            pts1.AddRange(OutChildren_Point);
            if (line.InPoints != null)
            {
                pts2.AddRange(line.InPoints);
            }
            pts2.AddRange(line.OutChildren_Point);
            foreach (PointBase pt1 in pts1)
            {
                foreach (PointBase pt2 in pts2)
                {
                    if (pt1 == pt2)
                    {
                        return pt1;
                    }
                }
            }
            PointCollectionBase[] pcs = PointCollectionBase.Intersect(PSkeleton.PointCollections, line.PSkeleton.PointCollections);
            if (pcs == null) return null;
            PointD? dest = pcs.GetNearestPoint(pske.Location);
            if (!dest.HasValue) return null;
            CrossoverPointsManager_B mgr = null;
            IEnumerable<ComponentManagerBase> jj = ComponentManagers.Intersect(line.ComponentManagers);
            foreach (ComponentManagerBase item in jj)
            {
                mgr = item as CrossoverPointsManager_B;
                if (mgr != null)
                {
                    break;
                }
            }
            if (mgr != null || Pad.TryCreateObject(mgr = new CrossoverPointsManager_B(Pad, string.Format("{0}({1}, {2})", Resources.Translate("CrossoverPoint"), Name, line.Name), this, line)))
            {
                CrossoverPointBase pt;
                if (Pad.CoordinateType == CoordinateSystemType.PolarCoordinate)
                {
                    pt = new PCrossoverPoint(Pad, Pad.StdObjName("CrossoverPoint"), this, line, mgr);
                }
                else
                {
                    pt = new RCrossoverPoint(Pad, Pad.StdObjName("CrossoverPoint"), this, line, mgr);
                }
                pt.NewPxy = dest.Value;
                if (Pad.TryCreateObject(pt))
                {
                    pt.OrderIndex = Pad.PointOrder++;
                    mgr.Points.Add(pt);
                    return pt;
                }
            }
            return null;
        }
        //public override bool OnObject(PointD pt)
        //{
        //    return pt.DistanceTo(A, B, C).AlmostEqual(0);
        //}
        public override void SetPoint(int index, PointBase value)
        {
            base.SetPoint(index, value);
            if (value != null)
            {
                switch (index)
                {
                    case 0:
                        ske.Location = value.ToPointD();
                        pske.Location = value.Pxy;
                        break;
                }
            }
        }
        //public override PointD SlideDestination(PointD currentLocation, double length, out bool reverse)
        //{
        //    return NewSkeleton.SlideDestination(currentLocation, length, out reverse);
        //}
        public override PointD GetOutFreePointRecommendNewLocation(PointBase pt, bool getXY = true)
        {
            PointD[] pts = CurveIntersection.StraightLineCircleIntersection(NewA, NewB, NewC, NewLocation, ske.Location.DistanceTo(pt.ToPointD()));
            if (pts != null)
            {
                PointD xy = pt.ToPointD().GetNearestPoint(pts);
                return getXY ? xy : Pad.XYToPxy(xy);
            }
            return base.GetOutFreePointRecommendNewLocation(pt, getXY);
        }
        public override void ComponentBuilding(Base component)
        {
            AppendStatus($"，{Resources.Translate("Slope")} = {Slope}{Resources.CommaSeparate}{Resources.Translate("SlopeAngle")}{Resources.ColonSeparate}{SlopeAngle}", true);
            base.ComponentBuilding(component);
        }
        public override void ComponentChanged(ShapeBase component)
        {
            ske.Location = NewLocation;
            pske.Location = NewPLocation;
            Slope = NewSlope;
            base.ComponentChanged(component);
        }
        protected override string getFormula()
        {
            if (Pad.CoordinateType == CoordinateSystemType.PolarCoordinate)
            {
                PointD rt = (Pad.Coordinate as PolarCoordinate).XYtoRT(Location, false);
                return $"ρsin({SlopeAngle}-θ)={rt.X * Math.Sin(SlopeAngle - rt.Y)}";
            }
            else
            {
                return $"{A}x+{B}y+{C}=0";
            }
        }
        public override PropertyDisplayer[] PropertyDisplayersCheckNearBy(PointD Plocation)
        {
            List<PropertyDisplayer> res = new List<PropertyDisplayer>(base.PropertyDisplayersCheckNearBy(Plocation));
            if (SlopeDisplayer.InRegion(Plocation))
            {
                res.Add(SlopeDisplayer);
            }
            if (SlopeAngleDisplayer.InRegion(Plocation))
            {
                res.Add(SlopeAngleDisplayer);
            }
            return res.ToArray();
        }
        //protected override PointD GetAbsorbedPoint(PointD PLocation, bool getNew = false)
        //{
        //    if (getNew)
        //    {
        //        return PLocation.GetProjectivePoint(NewPLocation, NewPSlope);
        //    }
        //    return PLocation.GetProjectivePoint(this.PLocation, PSlope);
        //}
    }
}
